package com.doge.walknovel.business.home.genres.rank

import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
import com.doge.walknovel.api.ApiResultMapTransformer
import com.doge.walknovel.api.ObserverAdapter
import com.doge.walknovel.api.api
import com.doge.walknovel.api.model.NovelInfo
import com.doge.walknovel.api.observeOnWork
import com.doge.walknovel.business.home.genres.GenresCacheHelper
import com.doge.walknovel.core.base.LoadStatus
import com.doge.walknovel.core.base.adapters.*
import com.doge.walknovel.utils.fromJson
import com.doge.walknovel.utils.isMainThread
import com.doge.walknovel.utils.runOnIO
import com.doge.walknovel.utils.runOnUI
import com.google.gson.reflect.TypeToken

class RankCellRepository(private val requestId: Int) {
    private val cacheKey = "r$requestId"
    val loading = MutableLiveData<LoadStatus>()
    private var state: ListState<NovelInfo> = ListState()
    private var dataSource: XPagedKeyDataSource? = null

    fun request(initial: Boolean) {
        require(isMainThread())

        runOnIO {
            val nextPage = if (initial) PAGE_INITIAL else (state.page + 1)

            if (initial && state.data.isEmpty()) {
                val disk: String? = GenresCacheHelper.loadCache(cacheKey)
                val cache =
                    fromJson<List<NovelInfo>>(disk, object : TypeToken<List<NovelInfo>>() {}.type)
                if (cache?.isNotEmpty() == true) {
                    reduceOnNext(initial, cache)
                } else {
                    runOnUI { loading.value = LoadStatus.LOADING }
                }
            }

            api.novelGenresRankList(requestId, nextPage)
                .map(ApiResultMapTransformer())
                .observeOnWork()
                .subscribe(object : ObserverAdapter<List<NovelInfo>>() {
                    override fun onNext(t: List<NovelInfo>) {
                        reduceOnNext(initial, t)
                        if (initial) {
                            GenresCacheHelper.saveCache(cacheKey, t)
                        }
                    }

                    override fun onError(e: Throwable) {
                        super.onError(e)
                        reduceOnError(initial)
                    }
                })
        }
    }

    private fun reduceOnNext(initial: Boolean, data: List<NovelInfo>) {
        require(!isMainThread())
        state = state.reduceOnNext(initial, data) {
            it.novelId.toString()
        }
        dataSource?.invalidate()
        runOnUI { loading.value = LoadStatus.SUCCESS }
    }

    private fun reduceOnError(initial: Boolean) {
        require(!isMainThread())
        state = state.reduceOnError(initial && state.data.isEmpty())
        dataSource?.invalidate()
        runOnUI { loading.value = LoadStatus.FAILURE }
    }

    fun dataSource(): DataSource.Factory<Int, Cell> =
        object : DataSource.Factory<Int, Cell>() {
            override fun create(): DataSource<Int, Cell> {
                return object : XPagedKeyDataSource() {
                    override fun totalCount(): Int {
                        return state.list.size
                    }

                    override fun loadRange(start: Int, count: Int): List<Cell?> {
                        return state.list.mapToCell(start, count) {
                            Cell(ItemType.RankCell, it, extra = state.data[it])
                        }
                    }
                }.also { dataSource = it }
            }
        }
}